# Lucid is (C) Copyright 2006-2007 by Infiscape Corporation
#
# This file is part of Lucid, a PyOpenSG example application.
#
# Lucid is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# Lucid is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.

from PyQt4 import QtGui, QtCore
import osg2
import OpenGL.GL
import lucid.core
import lucid.plugins.shader_editor.shader_editor as shader_editor
import field_edit_btn_base
from lucid.util.qt.property_tree import *
from pydispatch import dispatcher
import lucid.util.osg_helpers as osg_helpers


def addGenericFCProperties(propParent, fieldContainer):
   """ Adds generic Field container property editors to the propGroup passed. 
       Uses the fcd info to find the type and cardinality.
       If card == 'multi' then just use a dumb property for now.
   """   
   # Build up list of editors
   known_editor_types = [NumberProperty, GlEnumProperty, BoolProperty, 
                         StringProperty, VectorProperty, ColorProperty]
   
   field_editors = []   
   fc_type = fieldContainer.getType()
   
   fc_fcd = osg2.FcdReflector(fc_type, mergeParents=True)      
      
   for field in fc_fcd.fields():
      fname = field.getName()
      ftype = field.getType()
      fcard = field.getCardinality()
      fvis  = field.getVisibility()
      editor_type = DumbFieldProperty
         
      if "internal" == fvis:  # Skip internal fields
         continue
      
      if "multi" != fcard:
         if ftype.endswith("Ptr") and not ftype.startswith("Geo"):
            editor_type = FCPtrProperty
         else:
            for t in known_editor_types:
               if ftype in t.getFieldTypeStr():
                  editor_type = t         
      else:
         pass  # Just use dumb editor with multi for now.
      propParent.addChild( editor_type(propParent, fieldContainer, fname) )


# ---------  FC Field Property classes --------------- #
class FcFieldProperty(Property):
   """ Common base class for all OpenSG FC field properties. """
   def __init__(self, propGroup, fc, name):
      super(FcFieldProperty, self).__init__(name, propGroup)
      self.mFC = fc
      fc_type = fc.getType()      
      self.mDocumentation = ""
      self.mEditableName = False      
      self.mFcd = osg2.FcdReflector(fc_type, mergeParents=True)
      self.mFieldFcd = None      
      if not self.mFcd.hasData():
         self.mFcd = None
      else:
         self.mFieldFcd = self.mFcd.getField(name)
      self.mFieldDesc = fc_type.getFieldDesc(name)      
      
      #if self.mFieldFcd:
      #   self.mIsMF = (self.mFieldDesc.getCardinality() == "multi")
      #else:
      self.mIsMF = self.mFieldDesc.getFieldType().getCName().startswith("MF")
      
      (getter,setter) = self.getGetterAndSetterNames(name)
      self.mGetter = getattr(fc,getter,None)
      self.mSetter = getattr(fc,setter,None)      
      
      if self.mFieldFcd:
         self.mDocumentation = self.mFieldFcd.getDocumentation()      
         self.mDocumentation = self.mDocumentation.strip()
   
   def getGetterAndSetterNames(self, propName):
      prop_name_cap = propName[0].upper()+propName[1:]
      return ("get"+prop_name_cap,"set"+prop_name_cap)
   
   def tooltip(self):
      if self.mDocumentation and "" != self.mDocumentation:
         return self.mDocumentation
      else:
         return QtCore.QVariant()
   
   # --- Name Editing support --- #
   # Note: I don't like this at all it seems dirty and makes
   # the subclasses have to do more....
   def setEditableName(self, getter, setter):
      """ Makes it possible to edit the field name. """
      self.mEditableName = True
      self.nameGetter = getter
      self.nameSetter = setter
      
   def hasNameEditor(self,index):
      return self.mEditableName         

   def createNameEditor(self, parent, styleOpt, index):
      name_content = self.name
      line_edit = QtGui.QLineEdit(parent)
      line_edit.setText(name_content)
      return line_edit
         
   def setNameEditorData(self, widget, index):
      widget.setText(self.nameGetter())
         
   def setNameModelData(self,widget,model,index):
      self.nameSetter(str(widget.text()))
      self.name = self.nameGetter()
   
   @staticmethod
   def getFieldTypes():
      """ Return string list of type that we support. """
      return []


class DumbFieldProperty(FcFieldProperty):
   def __init__(self, propGroup, fc, name):
      super(DumbFieldProperty, self).__init__(propGroup,fc, name)

   def hasEditor(self,index):
      return False
   def value(self):
      if self.mGetter:
         data = self.mGetter()
         
         # XXX: Performance issue here, getting image pixels field is
         # a *very* heavy weight operation
         if self.mIsMF:  # Check size first
            if len(data) > 20:
               return "mfield: size: %s"%len(data)
         else:
            return data
      else:
         return "No Getter"



class NumberProperty(FcFieldProperty):
   """ Wrapper for properties of number types."""
   @staticmethod
   def getFieldTypeStr():
      return ["Real","Real32",
              "UInt32","UInt16","UInt8",
              "Int32","Int16","Int8"]
   
   def __init__(self, propGroup, fc, name, range=None):
      super(NumberProperty, self).__init__(propGroup, fc, name)
      self.mRange = range
      self.type_name = self.mFieldFcd.getType()
      
      if self.mFieldFcd and not self.mRange:
         if self.mFieldFcd.attrib.has_key('min_value') and self.mFieldFcd.attrib.has_key('max_value'):
               min_value = float(self.mFieldFcd.attrib["min_value"])
               max_value = float(self.mFieldFcd.attrib["max_value"])
               inc = (max_value-min_value)/100.0
               self.mRange=(min_value,max_value,inc)

      if not self.mRange:
         self.mRange = self.getDefaultRange(self.type_name)

   def hasEditor(self,index):
      return True
   def value(self):
      return self.mGetter()
   def createEditor(self, parent, styleOpt, index):
      if self.type_name.startswith("Real"):
         editor = QtGui.QDoubleSpinBox(parent)
      else:
         editor = QtGui.QSpinBox(parent)
      editor.setMinimum(self.mRange[0])
      editor.setMaximum(self.mRange[1])
      editor.setSingleStep(self.mRange[2])
      editor.connect(editor, QtCore.SIGNAL("valueChanged(double)"), self.onValueChanged)
      editor.connect(editor, QtCore.SIGNAL("valueChanged(int)"), self.onValueChanged)
      return editor
   def setEditorData(self, widget, index):
      widget.setValue(self.mGetter())
   def setModelData(self,widget,model,index):
      value = widget.value()
      self.onValueChanged(value)      
   def onValueChanged(self, value):
      self.mSetter(value)
      dispatcher.send(lucid.core.signal.SelectedFieldContainerModified)
   
   def getDefaultRange(self, typeId):
      range_map = { "Real"   : [-9999999,9999999,0.05],
                    "Real32" : [-9999999,9999999,0.05],
                    "UInt32" : [0, pow(2,32)-1, 1],
                    "UInt16" : [0, pow(2,16)-1, 1],
                    "UInt8"  : [0, pow(2,8)-1,  1],
                    "Int32"  : [-(pow(2,31)-1), pow(2,31)-1, 1],
                    "Int16"  : [-(pow(2,15)-1), pow(2,15)-1, 1],
                    "Int8"   : [-(pow(2,8)-1),  pow(2,8)-1,  1] }
      return range_map[typeId]


class GlEnumProperty(FcFieldProperty):
   """ Wrapper for properties of type 'GLenum'. """
   @staticmethod
   def getFieldTypeStr():
      return ["GLenum"]

   def __init__(self, propGroup, fc, name):
      super(GlEnumProperty, self).__init__(propGroup,fc, name)      
      self.mPotentialValues = []    # List of [string,enum_val]
      
      if self.mFieldFcd and self.mFieldFcd.attrib.has_key('potential_values'):
         enums = self.mFieldFcd.attrib["potential_values"].split(",")
         enums = [e.strip() for e in enums]
         enums = [n for n in enums if OpenGL.GL.__dict__.has_key(n)]
      else:
         enums = [n for n in OpenGL.GL.__dict__ if n.startswith("GL_")\
                                        and type(OpenGL.GL.__dict__[n]) == int]

      for e in enums:
         self.mPotentialValues.append([e,OpenGL.GL.__dict__[e] ] )

   def indexOfEnumWithValue(self, val):
      """ Try to find the potential value with the given raw value.
          If found, return the index to that entry in pot values.
          Else, return None to signify it was not found.
      """
      for i in range(len(self.mPotentialValues)):
         if self.mPotentialValues[i][1] == val:
            return i
      return None

   # --- Delegate and property interface --- #
   def hasEditor(self,index):
      return True
   def value(self):
      index = self.indexOfEnumWithValue(self.mGetter())
      if index:
         return self.mPotentialValues[index][0]
      else:
         return self.mGetter()      

   def createEditor(self, parent, styleOpt, index):
      editor = QtGui.QComboBox(parent)
      for pv in self.mPotentialValues:
         editor.addItem(pv[0], QtCore.QVariant(int(pv[1])))
      editor.connect(editor, QtCore.SIGNAL("highlighted(int)"), self.onItemHighlighted)
      editor.setEditable(False)
      return editor
   def setEditorData(self, widget, index):
      cur_index = self.indexOfEnumWithValue(self.mGetter())
      if not cur_index:
         cur_index = 0
      widget.setCurrentIndex(cur_index)
   def setModelData(self,widget,model,index):
      self.onItemHighlighted(widget.currentIndex())
   
   def onItemHighlighted(self, index):
      self.mSetter(self.mPotentialValues[index][1])
      dispatcher.send(lucid.core.signal.SelectedFieldContainerModified)


class BoolProperty(FcFieldProperty):
   """ Wrapper for properties of type 'bool'. """
   @staticmethod
   def getFieldTypeStr():
      return ["bool"]
   
   def __init__(self, propGroup, fc, name):
      super(BoolProperty, self).__init__(propGroup, fc, name)            

   # --- Delegate and property interface --- #
   def hasEditor(self,index):
      return True
   def value(self):
      return self.mGetter()      

   def createEditor(self, parent, styleOpt, index):
      editor = QtGui.QComboBox(parent)
      editor.addItem("False")
      editor.addItem("True")
      editor.connect(editor, QtCore.SIGNAL("highlighted(int)"), self.onItemHighlighted)
      editor.setEditable(False)
      return editor
   def setEditorData(self, widget, index):
      cur_val = self.mGetter()
      if False == cur_val:
         widget.setCurrentIndex(0)
      else:
         widget.setCurrentIndex(0)      
   def setModelData(self,widget,model,index):
      self.onItemHighlighted(widget.currentIndex())
   
   def onItemHighlighted(self, index):
      if 0 == index:
         self.mSetter(False)
      else:
         self.mSetter(True)
      dispatcher.send(lucid.core.signal.SelectedFieldContainerModified)

class StringProperty(FcFieldProperty):
   """ Wrapper for properties of type 'std::string'. 
       Behaves differently when it thinks it may be a shader or a filename.
   """
   @staticmethod
   def getFieldTypeStr():
      return ["std::string"]

   def __init__(self, propGroup, fc, name):
      super(StringProperty, self).__init__(propGroup, fc, name)

      self.edit_type = "line"
      if type(fc) in [osg2.SHLChunk,]:
         self.edit_type = "editor"
      

   # --- Delegate and property interface --- #
   def hasEditor(self,index):
      return True
   def value(self):
      if "editor" == self.edit_type:
         if self.mGetter() == "":
            return "--<empty>--"
         else:
            return "--shader--"
      else:
         return self.mGetter()

   def createEditor(self, parent, styleOpt, index):
      text_content = self.mGetter()
      
      if "line" == self.edit_type:         
         line_edit = QtGui.QLineEdit(parent)
         line_edit.setText(text_content)
         return line_edit
      elif "editor" == self.edit_type:
         editor = FieldEditBtn(parent)
         editor.setLabelStr(self.value())
         editor.setEditCB(self.onOpenEditor)
         return editor
   
   def setEditorData(self, widget, index):
      if "line" == self.edit_type:
         widget.setText(self.mGetter())
         
   def setModelData(self,widget,model,index):
      if "line" == self.edit_type:
         self.mSetter(widget.text())
      dispatcher.send(lucid.core.signal.SelectedFieldContainerModified)
   
   def onOpenEditor(self):
      """ Called to open a text editor. """
      editor = shader_editor.ShaderEditor()
      editor.open(self.mFC, self.name, self.mGetter, self.mSetter)


class VectorProperty(FcFieldProperty):
   """ General wrapper for vector types. 
       It tries to automatically adapt itself to 
   """
   @staticmethod
   def getFieldTypeStr():
      return ["Vec2f","Vec3f","Vec4f",
              "Vec2r","Vec3r","Vec4r",
              "Pnt2f","Pnt3f",
              "Pnt2r","Pnt3r",
              "Quaternionr","Quaternionf"]
   
   def __init__(self, propGroup, fc, name):
      super(VectorProperty, self).__init__(propGroup, fc, name)

      # Try to determine size and type
      type_name = self.mFieldFcd.getType()
      if type_name.startswith("Quaternion"):
         self.mSize = 4
      else:
         self.mSize = int(type_name[-2])
      self.mType = type_name[-1]
      if 'r' == self.mType:
         self.mType = 'f'
      self.spinners = [None]*self.mSize

   def hasEditor(self,index):
      return True
   def value(self):
      return self.mGetter()
   def createEditor(self, parent, styleOpt, index):
      editor = QtGui.QWidget(parent)
      self.hlayout = QtGui.QHBoxLayout(editor)
      self.hlayout.setMargin(0)
      self.hlayout.setSpacing(0)
      spinner_type = QtGui.QDoubleSpinBox
      if not self.mType == "f":
         spinner_type = QtGui.QSpinBox
      
      for i in range(self.mSize):
         new_spinner = spinner_type(parent)
         new_spinner.setMinimum(-999999)
         new_spinner.setMaximum(999999)
         self.hlayout.addWidget(new_spinner)
         new_spinner.connect(new_spinner, QtCore.SIGNAL("valueChanged(double)"), self.onValueChanged)
         new_spinner.connect(new_spinner, QtCore.SIGNAL("valueChanged(int)"), self.onValueChanged)         
         self.spinners[i] = new_spinner
      return editor
   
   def setEditorData(self, widget, index):
      vec_val = self.mGetter()
      for i in range(self.mSize):
         self.spinners[i].setValue(vec_val[i])      
   def setModelData(self,widget,model,index):
      self.onValueChanged(0)
   def onValueChanged(self, value):
      vec_val = self.mGetter()
      for i in range(self.mSize):
         vec_val[i] = self.spinners[i].value()      
      self.mSetter(vec_val)
      dispatcher.send(lucid.core.signal.SelectedFieldContainerModified)


class ColorProperty(FcFieldProperty):
   """ General wrapper for color types. 
       It tries to automatically adapt itself to 
   """
   @staticmethod
   def getFieldTypeStr():
      return ["Color3f","Color4f",
              "Color3r","Color4r"]
   
   def __init__(self, propGroup, fc, name, range=None):
      super(ColorProperty, self).__init__(propGroup, fc, name)

      # Try to determine size and type
      type_name = self.mFieldFcd.getType()
      self.mSize = int(type_name[-2])
      self.spinners = [None]*self.mSize

   def hasEditor(self,index):
      return True
   
   def value(self):
      return self.mGetter()
   
   def createEditor(self, parent, styleOpt, index):
      editor = QtGui.QWidget(parent)
      self.hlayout = QtGui.QHBoxLayout(editor)
      self.hlayout.setMargin(0)
      self.hlayout.setSpacing(0)      
      
      for i in range(self.mSize):
         new_spinner = QtGui.QDoubleSpinBox(parent)
         new_spinner.setMinimum(0.0)
         new_spinner.setMaximum(1.0)
         new_spinner.setSingleStep(0.01)
         self.hlayout.addWidget(new_spinner)
         new_spinner.connect(new_spinner, QtCore.SIGNAL("valueChanged(double)"), self.onValueChanged)         
         self.spinners[i] = new_spinner
      return editor
   
   def setEditorData(self, widget, index):
      color = self.mGetter()
      for i in range(self.mSize):
         self.spinners[i].setValue(color[i])      
   def setModelData(self,widget,model,index):
      self.onValueChanged(0)
   def onValueChanged(self, value):
      color = self.mGetter()
      for i in range(self.mSize):
         color[i] = self.spinners[i].value()      
      self.mSetter(color)
      dispatcher.send(lucid.core.signal.SelectedFieldContainerModified)


class FCPtrProperty(FcFieldProperty):
   """ Wrapper for properties that are SFPtrs."""
   @staticmethod
   def getFieldTypeStr():
      return []

   def __init__(self, propGroup, fc, name):
      super(FCPtrProperty, self).__init__(propGroup,fc, name)            
      
      # List of types to lookup from type map
      self.availableTypeNames = []      
      field_type = self.mFieldFcd.getType()
      assert field_type.endswith("Ptr")
      field_type = field_type[:-3]
      osg_field_type = osg2.__dict__.get(field_type,None)
      
      if osg_field_type:
         self.availableTypeNames = [c.__name__ for c in osg2.__dict__.itervalues() \
                        if isinstance(c,type) and (osg_field_type in c.__mro__) and \
                        (not c.__name__.endswith("Base"))]

   # --- Delegate and property interface --- #
   def hasEditor(self,index):
      return True
   def value(self):
      ptr = self.mGetter()
      if not ptr:
         name = "NullFC"
      else:
         name = "<unamed> %s"%str(ptr)
         try:
            fc_name = osg_helpers.getFcName(ptr,None)
            if fc_name:
               name = fc_name
         except:
            pass
         
      return name

   def createEditor(self, parent, styleOpt, index):
      env = lucid.core.Environment()
      cur_scene = env.activeScene
      
      self._availableItems = []   # List of name, ptr
      for tname in self.availableTypeNames:
         items = cur_scene.mFcTypeMap.get(tname,[])
         for fc in items:
            fc_name = osg_helpers.getFcName(fc,"<unnamed>") 
            entry = ["%s [%s]"%(fc_name, fc.getType().getCName()), fc]
            self._availableItems.append(entry)      
      self._availableItems.sort()
      
      # Create combo box with name and user data is index into list
      editor = QtGui.QComboBox(parent)
      for i in range(0,len(self._availableItems)):
         editor.addItem(self._availableItems[i][0], QtCore.QVariant(int(i)))
      editor.connect(editor, QtCore.SIGNAL("highlighted(int)"), self.onItemHighlighted)
      editor.setEditable(False)
      return editor
   
   def setEditorData(self, widget, index):
      # Try to find current item and it's index
      cur_index = 0
      cur_fc = self.mGetter()
      for i in range(0,len(self._availableItems)):
         if self._availableItems[i][1] == cur_fc:
            cur_index = i
            break
      widget.setCurrentIndex(cur_index)
      
   def setModelData(self,widget,model,index):
      self.onItemHighlighted(widget.currentIndex())      
   
   def onItemHighlighted(self, index):
      print "Selected: ", self._availableItems[index][0]
      selected_fc = self._availableItems[index][1]      
      self.mSetter(selected_fc)
      dispatcher.send(lucid.core.signal.SelectedFieldContainerModified)


class FieldEditBtn(QtGui.QWidget, field_edit_btn_base.Ui_Form):
   """ Helper widget to display [label <spacer> toolbtn].
      The point it to use it when you have to edit in an external tool.
      Then connect the button to the method to open the tool.
   """
   def __init__(self,parent):
      QtGui.QWidget.__init__(self, parent)
      self.setupUi(self)
   
   def setLabelStr(self, val):
      self.mLabel.setText(val)
   
   def setEditCB(self, cb):
      self.connect(self.mEditBtn, QtCore.SIGNAL("pressed()"), cb)
      

extra_gl_enums = { "GL_DEPTH_COMPONENT16":0x81A5,
"GL_DEPTH_COMPONENT24":0x81A6,
"GL_DEPTH_COMPONENT32":0x81A7,

"GL_COLOR_ATTACHMENT0_EXT":0x8CE0,
"GL_COLOR_ATTACHMENT1_EXT":0x8CE1,
"GL_COLOR_ATTACHMENT2_EXT":0x8CE2,
"GL_COLOR_ATTACHMENT3_EXT":0x8CE3,
"GL_COLOR_ATTACHMENT4_EXT":0x8CE4,
"GL_COLOR_ATTACHMENT5_EXT":0x8CE5,
"GL_COLOR_ATTACHMENT6_EXT":0x8CE6,
"GL_COLOR_ATTACHMENT7_EXT":0x8CE7,
"GL_COLOR_ATTACHMENT8_EXT":0x8CE8,
"GL_COLOR_ATTACHMENT9_EXT":0x8CE9,
"GL_COLOR_ATTACHMENT10_EX":0x8CEA,
"GL_COLOR_ATTACHMENT11_EX":0x8CEB,
"GL_COLOR_ATTACHMENT12_EX":0x8CEC,
"GL_COLOR_ATTACHMENT13_EX":0x8CED,
"GL_COLOR_ATTACHMENT14_EX":0x8CEE,
"GL_COLOR_ATTACHMENT15_EX":0x8CEF,

"GL_CLAMP_TO_EDGE":0x812F,
"GL_COMPARE_R_TO_TEXTURE":0x884E,
}

for (k,v) in extra_gl_enums.iteritems():
   if not OpenGL.GL.__dict__.has_key(k):
      OpenGL.GL.__dict__[k] = v

   
